package com.cgrs572.shortlink.admin.common.biz.user;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import java.util.Objects;

import static com.cgrs572.shortlink.admin.common.constant.RedisCacheConstant.USER_LOGIN_KEY;
//import static com.nageoffer.shortlink.admin.common.enums.UserErrorCodeEnum.USER_TOKEN_FAIL;

/**
 * 用户信息传输过滤器
 * 注意：该类是优化前未添加网关路由过滤器时的写法，新写法可详见UserTransmitFilter
 * 即前端传来请求会先经网关路由过滤器，然后判断是否是指定的白名单uri，若是则直接放行进入到后管服务过滤器中；若不是则
 * 此时就需要通过用户的用户名和Token以及缓存来判断是否已登录，若已登录则向请求头中添加用户id和用户真是姓名并放行；若用户名
 * 或Token或缓存出错则代表登录信息错误，此时构建构建好的错误响应并返回给前端，前端收到响应后会自行处理
 */
@RequiredArgsConstructor
public class UserTransmitFilter0 implements Filter {
    private final StringRedisTemplate stringRedisTemplate; // 用于操作Redis的工具类
    // 定义直接放行的uri
    private static final List<String> ignoreURI = Lists.newArrayList(
            "/api/short-link/v1/user/login", // 用户登录
            "/api/short-link/admin/v1/user/has-username" // 检查用户名是否可用
            //  "/api/short-link/admin/v1/user"---用户注册---需要注意的是用户修改用到的也是这个uri，所以需特殊处理
    );

    /**
     * 注意：SpringMvc的全局异常拦截器无法拦截到过滤器抛出的异常，原因如下：
     * 过滤器拦截到的请求是在进入SpringMvc之前的，而异常拦截器是基于SpringMvc的，它只会处理SpringMvc控制器方法所抛出的异常
     */
    @Override
    @SneakyThrows // 作用：忽略异常
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        String requestURI = httpServletRequest.getRequestURI();
        // if (!Objects.equals(requestURI, "/api/short-link/v1/user/login")) --- 用于一个uri判断
        if (!ignoreURI.contains(requestURI)) { // 用于多个uri判断---若不是指定的uri的话则必须执行放行前的操作，否则直接放行
            String method = httpServletRequest.getMethod();
            // 判断是否是用户注册---若不是则执行放行前策略，反之则直接放行
            if (!(Objects.equals(requestURI, "/api/short-link/admin/v1/user") && Objects.equals(method, "POST"))) {
                String username = httpServletRequest.getHeader("username");
                String token = httpServletRequest.getHeader("token");
                // 判断是否为空，注意采用StrUtil.isAllNotBlank,而不是StrUtil.isAllNotEmpty，因为前者会将字符串中的空格去除，而后者会将空格也当作字符串
                // 此处是未了防止用户未登录就访问相关信息
                if (!StrUtil.isAllNotBlank(username, token)) {
                    // TUDO 若为空则跳转到登录界面
//                    returnJson((HttpServletResponse)servletResponse, JSON.toJSONString(Results.failure(new ClientException(USER_TOKEN_FAIL))));
                    return;
                    // throw new ClientException(USER_TOKEN_FAIL);
                }
                // 获取用户对象(即UserDO)
                Object userInfoJsonStr;
                try {
                    // 若已登录则需要进一步判断当前用户的Token是否已Token失效，若失效则重新跳转到登陆界面(注意：失效后会被删除，此时获取的对象为null)
                    // Redis采用惰性删除和定期删除两种方式来处理过期token
                    userInfoJsonStr = stringRedisTemplate.opsForHash().get(USER_LOGIN_KEY + username, token);
                    if (userInfoJsonStr == null) {
                        // TUDO 若失效则跳转到登录界面
//                        returnJson((HttpServletResponse)servletResponse, JSON.toJSONString(Results.failure(new ClientException(USER_TOKEN_FAIL))));
                        return;
                        // throw new ClientException(USER_TOKEN_FAIL);
                    }
                } catch (Exception e) {
                    // 此处try...catch块是为了防止Redis报错,若redis报错则执行catch块
                    // redis报异常的情况：Redis连接失败、配置问题、接收对象与redis传出的对象不一致（即序列化错误）、redis内存溢出或资源限制、get方法参数为null或空字符串
//                    returnJson((HttpServletResponse)servletResponse, JSON.toJSONString(Results.failure(new ClientException(USER_TOKEN_FAIL))));
                    return;
                    // throw new ClientException(USER_TOKEN_FAIL);
                }
                // 此处可以使用BeanUtil.toBean，但需要注意的是BeanUtil不能直接将JSON 字符串转为对应的对象，而JSON.parseObject可以
                UserInfoDTO userInfoDTO = JSON.parseObject(userInfoJsonStr.toString(), UserInfoDTO.class);
                UserContext.setUser(userInfoDTO);// 将userInfoDTO存储到当前用户上下文中
            }
        }
        try {
            // 过滤器放行
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            UserContext.removeUser();
        }
    }

    /**
     * 注意：SpringMvc的全局异常拦截器无法拦截到过滤器抛出的异常，原因如下：
     * 过滤器拦截到的请求是在进入SpringMvc之前的，而异常拦截器是基于SpringMvc的，它只会处理SpringMvc控制器方法所抛出的异常
     * 因此为了解决该问题，暂时以以下方式代替throw解决问题，后续会进行优化处理
     */
    private void returnJson(HttpServletResponse response, String json) throws Exception{
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");
        try {
            writer = response.getWriter();
            writer.print(json);

        } catch (IOException e) {
        } finally {
            if (writer != null)
                writer.close();
        }
    }
}